Introduction
Sometime, you can find interesting data for a project on the web, and
quite often it will be possible to download the raw data set,like a
spreadsheet, csv or json, for your own use. However, this is not always
the case. And in these kind of cases, there is always the possibility to
try web-scrapping. While it can seem like a lengthy extra step towards
the realization of your intended idea, it can also give you the
opportunity to develop a new skill, and understand better how websites
work in general.
Web scrapping is generally legal, so you shouldn’t feel like you are
doing something wrong when scrapping data. Occasionally, some web sites
will have protection against it, but you still can manage to get what
you want. This post will show an example of using the rvest package (
another great package from the tidyverse team) to scrap
data from the website of michelin restaurants. Their
data is not accessible online in raw format, so we have a true
real-world case in hands.
Set up
Let’s include the libraries needed for this task.
library(tidyverse)
library(rvest)
library(leaflet)
and that’s it !
Now we will investigate the websites content at this link
(depending on which country you are connecting from, this page can have
a different aspect and language, but we keep the english version of the
site).

Now, to start getting into the real thing, we need to open the web
inspector tool that is included in most browsers. Usually, under the
development tab. On a mac and with Safari, you can acces it by pressing
COMMAND+ALT+I. Now to make life simpler, we turn on the element
selection mode by pressing SHIFT+COMMAND+C. If you do this with
safari on a mac, you should now be seeing something like that:

This way we can inspect the CSS and HTML elements and get their
content. For example, when we navigate to the card of a restaurant and
click on it we will see a pop-up like on the following image, and the
corresponding part of the html code in the inspector will be
highlighted. From then, we can expand the div element and navigate to
the last one of interest to us.

This way we can identify the div elements containing some relevant
information under their specific class:
.restaurant-details__heading--title for the
name.
.card__menu-footer--price for the
style.
.card__menu which contains attributes for the
coordinates.
.link for the link to the page of
the restaurant.
Scrapping
The rvest
package provides a set of functions that gets the data from an html page
as requested by the user.
Steps
First, we read the whole html code of the page via the
read_html function, then we specify the class of the
element we want to read from. Second, we specify whether we want the
whole content of the element or just the text contained inside of it.
This is done with the functions html_text2 or
html_text respectively. We will be using the first one.
Additionally, we can get attributes of the elements, this is done
through the html_attr function. For each of these function,
we specify as argument the attribute of interest found earlier. Finally,
we need to combine these function in order to get the data, this is done
by chaining the operations with the pipe operator,|>,
from tidyverse.
Reading the
website
michelin_url <- "https://guide.michelin.com/gb/en/restaurants"
html <- read_html(michelin_url)
Putting it
together
Next, the individual variables are combined in a single data set.
michelin_guide_scrap <- data.frame(name
,"lat"=as.numeric(lat)
,"lng"=as.numeric(lng)
,style
,pre_link)
Note that the link that we extract is the relative path of the page,
so we paste the missing bit of the absolute path before it.
Now, let’s visualize the result:
Et voilà !
The next steps now could be to look at each restaurants dedicated
page and see if there is some more info that can be scrapped from there
! Additionally, there are almost 800 pages on the website that list all
the restaurants the michelin guide has reviewed, so there is some need
to build extra functions to automate the scrapping of all the data.
To conclude…
We can go further, and visualize the results on a map using the
latitude and longitude of each restaurants we scrapped with a simple
leaflet map.
lng1 <- min(michelin_guide_scrap$lng)
lng2 <- max(michelin_guide_scrap$lng)
lat1 <- min(michelin_guide_scrap$lat)
lat2 <- max(michelin_guide_scrap$lat)
popup_name <- michelin_guide_scrap$name
popup_style <- michelin_guide_scrap$style
popup_link <- michelin_guide_scrap$pre_link
leaflet(data = michelin_guide_scrap
,options = leafletOptions(maxZoom = 14
# ,minZoom = 6
,worldCopyJump = TRUE
)
) |>
addTiles() |>
fitBounds(lng1 = lng1
,lat1 = lat1
,lng2 = lng2
,lat2 = lat2) |>
addCircleMarkers(lng = ~lng
,lat = ~lat
,radius = 2
,color = "black"
,opacity = 1
,fillColor = "black"
,fillOpacity = 1
,popup = paste0("<h5>"
,popup_name
,"</h5>"
,"<hr>"
,"<h6> Style: "
,popup_style
,"</h6>"
,"<p> <a href = "
,popup_link
," target = '_blank' > Restaurant page </a>")
,popupOptions = popupOptions(minWidth = "10%"
,keepInView = TRUE)
,label = ~name)
For a more advanced visualization of the full data set, check the shiny app.
LS0tCnRpdGxlOiAiV2ViIHNjcmFwcGluZyBpbiBSIgphdXRob3I6ICJJdmFubiBTY2hsb3NzZXIiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDogCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgY3NzOiBzdHlsZV9ybWQuY3NzCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgY29kZV9kb3dubG9hZDogeWVzCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICBmaWdfd2lkdGg6IDYKICAgIGZpZ19oZWlnaHQ6IDQKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIGRmX3ByaW50OiBrYWJsZQogICAgdGhlbWU6IHlldGkKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCmtuaXQ6IChybWFya2Rvd246OnJlbmRlcihpbnB1dD0ibWljaGVsaW5fd2ViX3NjcmFwcGluZy9taWNoZWxpbl9kYXRhX2VuZ2luZWVyaW5nLlJtZCIsb3V0cHV0X2ZpbGU9ImluZGV4Lmh0bWwiKSkKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRSwgZWNobz1GQUxTRX0KCmxpYnJhcnkoZm9ybWF0UikKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFCiAgICAgICAgICAgICAgICAgICAgICAsZXZhbCA9IFRSVUUKICAgICAgICAgICAgICAgICAgICAgICxpbmNsdWRlID0gVFJVRQogICAgICAgICAgICAgICAgICAgICAgLG1lc3NhZ2UgPSBGQUxTRQogICAgICAgICAgICAgICAgICAgICAgLHdhcm5pbmcgPSBGQUxTRQogICAgICAgICAgICAgICAgICAgICAgLGZpZ19jYXB0aW9uID0gRkFMU0UKICAgICAgICAgICAgICAgICAgICAgICxvdXQud2lkdGggPSAiODAlIgogICAgICAgICAgICAgICAgICAgICAgLGZpZy5hbGlnbiA9ICJjZW50ZXIiCiAgICAgICAgICAgICAgICAgICAgICAjICx0aWR5ID0gVFJVRQogICAgICAgICAgICAgICAgICAgICAgLGNhY2hlID0gRkFMU0UpCgpgYGAKCiMgSW50cm9kdWN0aW9uCgpTb21ldGltZSwgeW91IGNhbiBmaW5kIGludGVyZXN0aW5nIGRhdGEgZm9yIGEgcHJvamVjdCBvbiB0aGUgd2ViLCBhbmQgcXVpdGUgb2Z0ZW4gaXQgd2lsbCBiZSBwb3NzaWJsZSB0byBkb3dubG9hZCB0aGUgcmF3IGRhdGEgc2V0LGxpa2UgYSBzcHJlYWRzaGVldCwgY3N2IG9yIGpzb24sIGZvciB5b3VyIG93biB1c2UuIEhvd2V2ZXIsIHRoaXMgaXMgbm90IGFsd2F5cyB0aGUgY2FzZS4gQW5kIGluIHRoZXNlIGtpbmQgb2YgY2FzZXMsIHRoZXJlIGlzIGFsd2F5cyB0aGUgcG9zc2liaWxpdHkgdG8gdHJ5IHdlYi1zY3JhcHBpbmcuIFdoaWxlIGl0IGNhbiBzZWVtIGxpa2UgYSBsZW5ndGh5IGV4dHJhIHN0ZXAgdG93YXJkcyB0aGUgcmVhbGl6YXRpb24gb2YgeW91ciBpbnRlbmRlZCBpZGVhLCBpdCBjYW4gYWxzbyBnaXZlIHlvdSB0aGUgb3Bwb3J0dW5pdHkgdG8gZGV2ZWxvcCBhIG5ldyBza2lsbCwgYW5kIHVuZGVyc3RhbmQgYmV0dGVyIGhvdyB3ZWJzaXRlcyB3b3JrIGluIGdlbmVyYWwuICAgCldlYiBzY3JhcHBpbmcgaXMgZ2VuZXJhbGx5IGxlZ2FsLCBzbyB5b3Ugc2hvdWxkbid0IGZlZWwgbGlrZSB5b3UgYXJlIGRvaW5nIHNvbWV0aGluZyB3cm9uZyB3aGVuIHNjcmFwcGluZyBkYXRhLiBPY2Nhc2lvbmFsbHksIHNvbWUgd2ViIHNpdGVzIHdpbGwgaGF2ZSBwcm90ZWN0aW9uIGFnYWluc3QgaXQsIGJ1dCB5b3Ugc3RpbGwgY2FuIG1hbmFnZSB0byBnZXQgd2hhdCB5b3Ugd2FudC4KVGhpcyBwb3N0IHdpbGwgc2hvdyBhbiBleGFtcGxlIG9mIHVzaW5nIHRoZSBbKipydmVzdCoqXShodHRwczovL3J2ZXN0LnRpZHl2ZXJzZS5vcmcpIHBhY2thZ2UgKCBhbm90aGVyIGdyZWF0IHBhY2thZ2UgZnJvbSB0aGUgKip0aWR5dmVyc2UqKiB0ZWFtKSB0byBzY3JhcCBkYXRhIGZyb20gdGhlIAp3ZWJzaXRlIG9mIFttaWNoZWxpbiByZXN0YXVyYW50c10oaHR0cHM6Ly9ndWlkZS5taWNoZWxpbi5jb20vZ2IvZW4pLiBUaGVpciBkYXRhIGlzIG5vdCBhY2Nlc3NpYmxlIG9ubGluZSBpbiByYXcgZm9ybWF0LCBzbyB3ZSBoYXZlIGEgdHJ1ZSByZWFsLXdvcmxkIGNhc2UgaW4gaGFuZHMuCgojIFNldCB1cAoKTGV0J3MgaW5jbHVkZSB0aGUgbGlicmFyaWVzIG5lZWRlZCBmb3IgdGhpcyB0YXNrLgoKYGBge3IgfQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocnZlc3QpCmxpYnJhcnkobGVhZmxldCkKCmBgYAphbmQgdGhhdCdzIGl0ICEgCgpOb3cgd2Ugd2lsbCBpbnZlc3RpZ2F0ZSB0aGUgd2Vic2l0ZXMgY29udGVudCBhdCBbdGhpc10oaHR0cHM6Ly9ndWlkZS5taWNoZWxpbi5jb20vZ2IvZW4vcmVzdGF1cmFudHMpIGxpbmsgKGRlcGVuZGluZyBvbiB3aGljaCBjb3VudHJ5IHlvdSBhcmUgY29ubmVjdGluZyBmcm9tLCB0aGlzIHBhZ2UgY2FuIGhhdmUgYSBkaWZmZXJlbnQgYXNwZWN0IGFuZCBsYW5ndWFnZSwgYnV0IHdlIGtlZXAgdGhlIGVuZ2xpc2ggdmVyc2lvbiBvZiB0aGUgc2l0ZSkuIAoKYGBge3IgZWNobz1GQUxTRX0KCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJ3d3cvbWljaGVsaW5fcmVzdGF1cmFudHMucG5nIikKCmBgYAoKCgpOb3csIHRvIHN0YXJ0IGdldHRpbmcgaW50byB0aGUgcmVhbCB0aGluZywgd2UgbmVlZCB0byBvcGVuIHRoZSB3ZWIgaW5zcGVjdG9yIHRvb2wgdGhhdCBpcyBpbmNsdWRlZCBpbiBtb3N0IGJyb3dzZXJzLiBVc3VhbGx5LCB1bmRlciB0aGUgZGV2ZWxvcG1lbnQgdGFiLiBPbiBhIG1hYyBhbmQgd2l0aCBTYWZhcmksIHlvdSBjYW4gYWNjZXMgaXQgYnkgcHJlc3NpbmcgKkNPTU1BTkQrQUxUK0kqLiBOb3cgdG8gbWFrZSBsaWZlIHNpbXBsZXIsIHdlIHR1cm4gb24gdGhlIGVsZW1lbnQgc2VsZWN0aW9uIG1vZGUgYnkgcHJlc3NpbmcgKlNISUZUK0NPTU1BTkQrQyouIElmIHlvdSBkbyB0aGlzIHdpdGggc2FmYXJpIG9uIGEgbWFjLCB5b3Ugc2hvdWxkIG5vdyBiZSBzZWVpbmcgc29tZXRoaW5nIGxpa2UgdGhhdDoKCmBgYHtyIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJ3d3cvbWljaGVsaW5fcmVzdF93ZWJfZXhwLnBuZyIpCmBgYAoKVGhpcyB3YXkgd2UgY2FuIGluc3BlY3QgdGhlIENTUyBhbmQgSFRNTCBlbGVtZW50cyBhbmQgZ2V0IHRoZWlyIGNvbnRlbnQuIEZvciBleGFtcGxlLCB3aGVuIHdlIG5hdmlnYXRlIHRvIHRoZSBjYXJkIG9mIGEgcmVzdGF1cmFudCBhbmQgY2xpY2sgb24gaXQgd2Ugd2lsbCBzZWUgYSBwb3AtdXAgbGlrZSBvbiB0aGUgZm9sbG93aW5nIGltYWdlLCBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgcGFydCBvZiB0aGUgaHRtbCBjb2RlIGluIHRoZSBpbnNwZWN0b3Igd2lsbCBiZSBoaWdobGlnaHRlZC4gRnJvbSB0aGVuLCB3ZSBjYW4gZXhwYW5kIHRoZSBkaXYgZWxlbWVudCBhbmQgbmF2aWdhdGUgdG8gdGhlIGxhc3Qgb25lIG9mIGludGVyZXN0IHRvIHVzLiAKCmBgYHtyIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJ3d3cvbWljaGVsaW5fZ3VpZGVfaW5zcGVjdG9yLnBuZyIpCmBgYAoKClRoaXMgd2F5IHdlIGNhbiBpZGVudGlmeSB0aGUgZGl2IGVsZW1lbnRzIGNvbnRhaW5pbmcgc29tZSByZWxldmFudCBpbmZvcm1hdGlvbiB1bmRlciB0aGVpciBzcGVjaWZpYyBjbGFzczoKCiogYC5yZXN0YXVyYW50LWRldGFpbHNfX2hlYWRpbmctLXRpdGxlYCBmb3IgdGhlICoqbmFtZSoqLgoKKiBgLmNhcmRfX21lbnUtZm9vdGVyLS1wcmljZWAgZm9yIHRoZSAqKnN0eWxlKiouCgoqIGAuY2FyZF9fbWVudWAgd2hpY2ggY29udGFpbnMgYXR0cmlidXRlcyBmb3IgdGhlICoqY29vcmRpbmF0ZXMqKi4KCiogYC5saW5rYCBmb3IgdGhlICoqbGluayoqIHRvIHRoZSBwYWdlIG9mIHRoZSByZXN0YXVyYW50LiAKCiMgU2NyYXBwaW5nCgpUaGUgW2BydmVzdGBdKGh0dHBzOi8vcnZlc3QudGlkeXZlcnNlLm9yZykgcGFja2FnZSBwcm92aWRlcyBhIHNldCBvZiBmdW5jdGlvbnMgdGhhdCBnZXRzIHRoZSBkYXRhIGZyb20gYW4gaHRtbCBwYWdlIGFzIHJlcXVlc3RlZCBieSB0aGUgdXNlci4gCgojIyBTdGVwcwpGaXJzdCwgd2UgcmVhZCB0aGUgd2hvbGUgaHRtbCBjb2RlIG9mIHRoZSBwYWdlIHZpYSB0aGUgYHJlYWRfaHRtbGAgZnVuY3Rpb24sIHRoZW4gd2Ugc3BlY2lmeSB0aGUgY2xhc3Mgb2YgdGhlIGVsZW1lbnQgd2Ugd2FudCB0byByZWFkIGZyb20uIFNlY29uZCwgd2Ugc3BlY2lmeSB3aGV0aGVyIHdlIHdhbnQgdGhlIHdob2xlIGNvbnRlbnQgb2YgdGhlIGVsZW1lbnQgb3IganVzdCB0aGUgdGV4dCBjb250YWluZWQgaW5zaWRlIG9mIGl0LiBUaGlzIGlzIGRvbmUgd2l0aCB0aGUgZnVuY3Rpb25zIGBodG1sX3RleHQyYCBvciBgaHRtbF90ZXh0YCByZXNwZWN0aXZlbHkuIFdlIHdpbGwgYmUgdXNpbmcgdGhlIGZpcnN0IG9uZS4gQWRkaXRpb25hbGx5LCB3ZSBjYW4gZ2V0IGF0dHJpYnV0ZXMgb2YgdGhlIGVsZW1lbnRzLCB0aGlzIGlzIGRvbmUgdGhyb3VnaCB0aGUgYGh0bWxfYXR0cmAgZnVuY3Rpb24uIEZvciBlYWNoIG9mIHRoZXNlIGZ1bmN0aW9uLCB3ZSBzcGVjaWZ5IGFzIGFyZ3VtZW50IHRoZSBhdHRyaWJ1dGUgb2YgaW50ZXJlc3QgZm91bmQgZWFybGllci4gRmluYWxseSwgd2UgbmVlZCB0byBjb21iaW5lIHRoZXNlIGZ1bmN0aW9uIGluIG9yZGVyIHRvIGdldCB0aGUgZGF0YSwgdGhpcyBpcyBkb25lIGJ5IGNoYWluaW5nIHRoZSBvcGVyYXRpb25zIHdpdGggdGhlIHBpcGUgb3BlcmF0b3IsYCB8PiBgLCBmcm9tICoqdGlkeXZlcnNlKiouICAKCiMjIyBSZWFkaW5nIHRoZSB3ZWJzaXRlCgpgYGB7cn0KCm1pY2hlbGluX3VybCA8LSAiaHR0cHM6Ly9ndWlkZS5taWNoZWxpbi5jb20vZ2IvZW4vcmVzdGF1cmFudHMiCgpodG1sIDwtIHJlYWRfaHRtbChtaWNoZWxpbl91cmwpCgpgYGAKCiMjIyBFeHRyYWN0aW5nIHRoZSByZWxldmFudCBlbGVtZW50cwoKYGBge3J9CgpuYW1lIDwtIGh0bWwgfD4KICBodG1sX2VsZW1lbnRzKCIuY2FyZF9fbWVudS1jb250ZW50LS10aXRsZSIpIHw+IAogIGh0bWxfdGV4dDIoKQoKYGBgCgpgYGB7ciBlY2hvPUZBTFNFLGluY2x1ZGU9RkFMU0V9CmhlYWQobmFtZSkKCmBgYAoKClRoaXMgaGFzIGNyZWF0ZWQgYSB2ZWN0b3IgY29udGFpbmluZyB0aGUgdGV4dCBvZiBhbGwgdGhlIGVsZW1lbnRzIHRoYXQgd2VyZSBmb3VuZCB1bmRlciB0aGUgY2xhc3Mgc3BlY2lmaWVkLCBgLmNhcmRfX21lbnUtY29udGVudC0tdGl0bGVgLiAKCldlIGNhbiBub3cgcGVyZm9ybSB0aGUgc2FtZSBzdGVwcyBmb3IgZWFjaCB2YWx1ZSBvZiBpbnRlcmVzdCBmb3IgYSByZXN0YXVyYW50IGFuZCBncm91cCBpdCBpbnRvIGEgdGliYmxlLgoKYGBge3J9Cm5hbWUgPC0gaHRtbCB8PgogIGh0bWxfZWxlbWVudHMoIi5jYXJkX19tZW51LWNvbnRlbnQtLXRpdGxlIikgfD4KICBodG1sX3RleHQyKCkKCnN0eWxlIDwtIGh0bWwgfD4KICBodG1sX2VsZW1lbnRzKCIuY2FyZF9fbWVudS1mb290ZXItLXByaWNlIikgfD4KICBodG1sX3RleHQyKCkgCgpsYXQgPC0gaHRtbCB8PgogIGh0bWxfZWxlbWVudHMoIi5jYXJkX19tZW51IikgfD4KICBodG1sX2F0dHIoImRhdGEtbGF0IikKCmxuZyA8LSBodG1sIHw+CiAgaHRtbF9lbGVtZW50cygiLmNhcmRfX21lbnUiKSB8PgogIGh0bWxfYXR0cigiZGF0YS1sbmciKQoKcHJlX2xpbmsgPC0gaHRtbCB8PgogIGh0bWxfZWxlbWVudHMoIi5saW5rIikgfD4KICBodG1sX2F0dHIoImhyZWYiKQoKcHJlX2xpbmsgPC0gcGFzdGUoImh0dHBzOi8vZ3VpZGUubWljaGVsaW4uY29tIixwcmVfbGlua1stYygxOjE4KV0sc2VwID0gIiIpCgpgYGAKCgojIyBQdXR0aW5nIGl0IHRvZ2V0aGVyCgpOZXh0LCB0aGUgaW5kaXZpZHVhbCB2YXJpYWJsZXMgYXJlIGNvbWJpbmVkIGluIGEgc2luZ2xlIGRhdGEgc2V0LgoKYGBge3J9CgptaWNoZWxpbl9ndWlkZV9zY3JhcCA8LSBkYXRhLmZyYW1lKG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsImxhdCI9YXMubnVtZXJpYyhsYXQpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCJsbmciPWFzLm51bWVyaWMobG5nKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICxzdHlsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICxwcmVfbGluaykKCgpgYGAKTm90ZSB0aGF0IHRoZSBsaW5rIHRoYXQgd2UgZXh0cmFjdCBpcyB0aGUgcmVsYXRpdmUgcGF0aCBvZiB0aGUgcGFnZSwgc28gd2UgcGFzdGUgdGhlIG1pc3NpbmcgYml0IG9mIHRoZSBhYnNvbHV0ZSBwYXRoIGJlZm9yZSBpdC4gCgpOb3csIGxldCdzIHZpc3VhbGl6ZSB0aGUgcmVzdWx0OgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJyxlY2hvPUZBTFNFfQprbml0cjo6a2FibGUoaGVhZChtaWNoZWxpbl9ndWlkZV9zY3JhcCkKICAgICAgICAgICAgICxjYXB0aW9uID0gIlNjcmFwcGVkIGRhdGEiCiAgICAgICAgICAgICAsYWxpZ24gPSAibCIpIAoKYGBgCgpFdCB2b2lsw6AgIQoKVGhlIG5leHQgc3RlcHMgbm93IGNvdWxkIGJlIHRvIGxvb2sgYXQgZWFjaCByZXN0YXVyYW50cyBkZWRpY2F0ZWQgcGFnZSBhbmQgc2VlIGlmIHRoZXJlIGlzIHNvbWUgbW9yZSBpbmZvIHRoYXQgY2FuIGJlIHNjcmFwcGVkIGZyb20gdGhlcmUgISAKQWRkaXRpb25hbGx5LCB0aGVyZSBhcmUgYWxtb3N0IDgwMCBwYWdlcyBvbiB0aGUgd2Vic2l0ZSB0aGF0IGxpc3QgYWxsIHRoZSByZXN0YXVyYW50cyB0aGUgbWljaGVsaW4gZ3VpZGUgaGFzIHJldmlld2VkLCBzbyB0aGVyZSBpcyBzb21lIG5lZWQgdG8gYnVpbGQgZXh0cmEgZnVuY3Rpb25zIHRvIGF1dG9tYXRlIHRoZSBzY3JhcHBpbmcgb2YgYWxsIHRoZSBkYXRhLiAKCiMgVG8gY29uY2x1ZGUuLi4KCldlIGNhbiBnbyBmdXJ0aGVyLCBhbmQgdmlzdWFsaXplIHRoZSByZXN1bHRzIG9uIGEgbWFwIHVzaW5nIHRoZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIG9mIGVhY2ggcmVzdGF1cmFudHMgd2Ugc2NyYXBwZWQgd2l0aCBhIHNpbXBsZSBgbGVhZmxldGAgbWFwLgoKCmBgYHtyIH0KCmxuZzEgPC0gbWluKG1pY2hlbGluX2d1aWRlX3NjcmFwJGxuZykKbG5nMiA8LSBtYXgobWljaGVsaW5fZ3VpZGVfc2NyYXAkbG5nKQoKbGF0MSA8LSAgbWluKG1pY2hlbGluX2d1aWRlX3NjcmFwJGxhdCkKbGF0MiA8LSBtYXgobWljaGVsaW5fZ3VpZGVfc2NyYXAkbGF0KQoKcG9wdXBfbmFtZSA8LSBtaWNoZWxpbl9ndWlkZV9zY3JhcCRuYW1lCnBvcHVwX3N0eWxlIDwtIG1pY2hlbGluX2d1aWRlX3NjcmFwJHN0eWxlCnBvcHVwX2xpbmsgPC0gbWljaGVsaW5fZ3VpZGVfc2NyYXAkcHJlX2xpbmsKCmxlYWZsZXQoZGF0YSA9IG1pY2hlbGluX2d1aWRlX3NjcmFwCiAgICAgICAgLG9wdGlvbnMgPSBsZWFmbGV0T3B0aW9ucyhtYXhab29tID0gMTQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgLG1pblpvb20gPSA2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsd29ybGRDb3B5SnVtcCA9IFRSVUUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICApIHw+IAogIGFkZFRpbGVzKCkgfD4KICBmaXRCb3VuZHMobG5nMSA9IGxuZzEKICAgICAgICAgICAgLGxhdDEgPSBsYXQxCiAgICAgICAgICAgICxsbmcyID0gbG5nMgogICAgICAgICAgICAsbGF0MiA9IGxhdDIpIHw+IAogIGFkZENpcmNsZU1hcmtlcnMobG5nID0gfmxuZwogICAgICAgICAgICAgICAgICAgLGxhdCA9IH5sYXQKICAgICAgICAgICAgICAgICAgICxyYWRpdXMgPSAyCiAgICAgICAgICAgICAgICAgICAsY29sb3IgPSAiYmxhY2siCiAgICAgICAgICAgICAgICAgICAsb3BhY2l0eSA9IDEKICAgICAgICAgICAgICAgICAgICxmaWxsQ29sb3IgPSAiYmxhY2siCiAgICAgICAgICAgICAgICAgICAsZmlsbE9wYWNpdHkgPSAxCiAgICAgICAgICAgICAgICAgICAscG9wdXAgPSBwYXN0ZTAoIjxoNT4iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLHBvcHVwX25hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIjwvaDU+IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwiPGhyPiIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIjxoNj4gU3R5bGU6ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAscG9wdXBfc3R5bGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIjwvaDY+IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwiPHA+IDxhIGhyZWYgPSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLHBvcHVwX2xpbmsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIiB0YXJnZXQgPSAnX2JsYW5rJyA+IFJlc3RhdXJhbnQgcGFnZSA8L2E+IikKICAgICAgICAgICAgICAgICAgICxwb3B1cE9wdGlvbnMgPSBwb3B1cE9wdGlvbnMobWluV2lkdGggPSAiMTAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsa2VlcEluVmlldyA9IFRSVUUpCiAgICAgICAgICAgICAgICAgICAsbGFiZWwgPSB+bmFtZSkKCmBgYAoKRm9yIGEgbW9yZSBhZHZhbmNlZCB2aXN1YWxpemF0aW9uIG9mIHRoZSBmdWxsIGRhdGEgc2V0LCBjaGVjayB0aGUgW3NoaW55IGFwcF0oaHR0cHM6Ly9zY2hsb3NzZXIuc2hpbnlhcHBzLmlvL21pY2hlbGluX2d1aWRlLykuCgoKCgo=